﻿using zijian666.Data.SqlBuilders;
using zijian666.Data.SqlElements;

namespace zijian666.Data;

internal class DbParameterFormatProvider : IFormatProvider, ICustomFormatter, IPooledObject
{
    private IDbCommand? _cmd;
    private ISqlBuilder? _builder;
    private readonly StringBuilder _buffer = new();
    private readonly FormattableSql _sql = new();
    private readonly List<IDataParameter> _parameters = [];

    [MemberNotNull(nameof(_cmd), nameof(_builder))]
    public void SetCommand(IDbCommand command)
    {
        if (command?.Connection is null)
        {
            throw new ArgumentNullException($"{nameof(command)}.{nameof(command.Connection)}");
        }
        _cmd = command;
        _builder = command.Connection.GetSqlBuilder();
    }

    public object? GetFormat(Type? formatType) => this;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="arg"></param>
    /// <param name="format">
    /// #/! 数据库常量
    /// ##/!! 数据库对象名称
    /// : 数据库变量类型
    /// & 连接符
    /// =/<>/!= 比较符
    /// </param>
    /// <returns></returns>
    public string Format(string? format, object? arg, IFormatProvider? _)
    {
        format = format ?? "";

        string str;
        if (arg is IEnumerable e && !arg.IsSimpleValue())
        {
            var flag = format.Length > 1 && format[0] is '&';
            var connector = flag is true ? format[1..] : ", ";
            format = format.Trim();
            var ee = e.GetEnumerator();
            if (ee.MoveNext())
            {
                var p = FormatItem(ee.Current, flag ? "" : format);
                if (!ee.MoveNext())
                {
                    return p;
                }
                var begin = _buffer.Length;
                _buffer.Append(p);
                do
                {
                    _buffer.Append(connector);
                    _buffer.Append(FormatItem(ee.Current, flag ? "" : format));
                } while (ee.MoveNext());
                var sql = _buffer.ToString(begin, _buffer.Length - begin);
                _buffer.Length = begin;
                return sql;
            }
            str = FormatItem(null, format);
        }
        else
        {
            format = format.Trim();
            str = FormatItem(arg, format);
        }

        if (str is "NULL")
        {
            return format switch
            {
                "=" => "IS NULL",
                "!=" or "<>" => "IS NOT NULL",
                _ => "NULL",
            };
        }

        return format switch
        {
            "=" => "= " + str,
            "!=" => "!= " + str,
            "<>" => "<> " + str,
            _ => str,
        };
    }

    private string FormatItem(object? arg, string format)
    {
        switch (arg)
        {
            case FormatValue value:
                arg = value.Arg;
                format = value.Format ?? "";
                break;
            case FormatSqlName name:
                arg = name.Name ?? "";
                format = "##";
                break;
            case FormatConst @const:
                arg = @const.Const ?? "";
                format = "#";
                break;

            default:
                break;
        }

        var formattable = arg as FormattableString ?? (arg as ISqlElement)?.ToSql();
        if (formattable is not null)
        {
            return string.Format(this, formattable.Format, formattable.GetArguments());
        }

        if (arg is not IDataParameter parameter)
        {
            if (format is "!" or "#")
            {
                return arg?.ToString() ?? "NULL";
            }
            if (format is "!!" or "##")
            {
                if (arg?.ToString() is not { } str)
                {
                    return "NULL";
                }

                if (_cmd?.Connection?.GetSqlBuilder() is { } builder)
                {
                    str = builder.QuoteIdentifier(str);
                }

                return str;
            }

            arg = arg.GetDbObject();
            parameter = _builder?.CreateParameter(arg, (format?.Length > 1 && format[0] == ':') ? format[1..] : null)!;
        }
        else
        {
            arg = parameter.Value;
        }
        if (arg is null or DBNull)
        {
            return "NULL";
        }
        string placeholder;
        if (parameter is not null)
        {
            if (!_parameters.Contains(parameter))
            {
                _parameters.Add(parameter);
            }
            if (string.IsNullOrEmpty(parameter.ParameterName))
            {
                parameter.ParameterName = "p" + (_parameters.Count + _cmd!.Parameters.Count);
            }
            placeholder = _builder!.GetParameterHolder(parameter.ParameterName);
        }
        else
        {
            placeholder = $"'{arg}'";
        }
        return placeholder;
    }

    void IPooledObject.OnRetrunning()
    {
        if (_cmd != null)
        {
            foreach (var p in _parameters)
            {
                if (!_cmd.Parameters.Contains(p))
                {
                    _cmd.Parameters.Add(p);
                }
            }
            _cmd = null;
        }
        if (_buffer.Length > 0)
        {
            _buffer.Clear();
        }
        if (_parameters.Count > 0)
        {
            _parameters.Clear();
        }
        _builder = null;
    }

}
